import struct
import ctypes
import socket
import time


def __checksum(buf):
    size = len(buf)
    position = 0
    result = 0
    while size > 1:
        result += struct.unpack('H', buf[position:position + 2])[0]
        position += 2
        size -= 2
    if size:
        result += struct.unpack('H', buf[position:position + 2])[0]
    result = (result >> 16) + (result & 0xffff)
    result += (result >> 16)
    return ctypes.c_ushort(~result).value


class DataIO(object):
    def __init__(self, datagram):
        self.datagram = datagram
        self.position = 0
        self.length = len(self.datagram)

    @property
    def bytesAvailable(self):
        return self.length - self.position

    def read_datagram(self, count):
        data = self.datagram[self.position:self.position + count]
        self.position += count
        return data


class IPHeader():
    version_ihl = 0
    dscp_ecn = 0
    total_length = 0
    identification = 0
    offset = 0
    ttl = 0
    protocol = 0
    checksum = 0
    source_address = 0
    dest_address = 0

    def deserialize(self, src):
        io = DataIO(src)
        self.version_ihl = struct.unpack('B', io.read_datagram(1))[0]
        self.dscp_ecn = struct.unpack('B', io.read_datagram(1))[0]
        self.total_length = struct.unpack('H', io.read_datagram(2))[0]
        self.identification = struct.unpack('H', io.read_datagram(2))[0]
        self.offset = struct.unpack('H', io.read_datagram(2))[0]
        self.ttl = struct.unpack('B', io.read_datagram(1))[0]
        self.protocol = struct.unpack('B', io.read_datagram(1))[0]
        self.checksum = struct.unpack('H', io.read_datagram(2))[0]
        self.source_address = struct.unpack('!L', io.read_datagram(4))[0]
        self.dest_address = struct.unpack('!L', io.read_datagram(4))[0]

    def show_info(self):
        print([self.version_ihl, self.dscp_ecn, self.total_length, self.identification, self.offset, self.ttl, self.protocol, self.checksum])
        self.show_ip_addr(self.source_address)
        self.show_ip_addr(self.dest_address)

    def show_ip_addr(self, word):
        print((word & 0xff000000) >> 24, (word & 0xff0000) >> 16, (word & 0xff00) >> 8, word & 0xff)
        
    @property
    def ihl(self):
        return (self.version_ihl & 0x0f) * 4


class ICMPHeader():
    type = 0
    code = 0
    checksum = 0
    id = 0
    sequence = 0
    timestamp = 0

    def serialize(self):
        return b''.join((
            struct.pack('B', self.type),
            struct.pack('B', self.code),
            struct.pack('H', self.checksum),
            struct.pack('H', self.id),
            struct.pack('H', self.sequence),
            struct.pack('L', int(self.timestamp)),
        ))

    def deserialize(self, src):
        io = DataIO(src)
        self.type = struct.unpack('B', io.read_datagram(1))[0]
        self.code = struct.unpack('B', io.read_datagram(1))[0]
        self.checksum = struct.unpack('H', io.read_datagram(2))[0]
        self.id = struct.unpack('H', io.read_datagram(2))[0]
        self.sequence = struct.unpack('H', io.read_datagram(2))[0]
        self.timestamp = struct.unpack('L', io.read_datagram(4))[0]


def ping(host, timeout=1):
    sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)
    header = ICMPHeader()
    header.type = 8
    header.id = 1
    header.sequence = 1
    header.timestamp = time.time()
    header.checksum = __checksum(header.serialize())
    sock.sendto(header.serialize(), (socket.gethostbyname(host), 0))
    try:
        sock.settimeout(timeout)
        data = sock.recvfrom(256)[0]
        resp_ipheader = IPHeader()
        resp_ipheader.deserialize(data)
        resp_header = ICMPHeader()
        resp_header.deserialize(data[resp_ipheader.ihl:])
        resp_ipheader.show_info()
        span = (int(time.time()) - header.timestamp) * 1000
        return int(span), resp_ipheader.ttl
    except socket.timeout:
        return None


if __name__ == '__main__':
    for i in range(4):
        if i:
            time.sleep(1)
        print (ping('192.168.1.10'))
